package jsr292.java.lang.invoke;

import jsr292.weaver.Debug;
import jsr292.weaver.opt.OptimizerContext;
import jsr292.weaver.opt.OptimizerSnippets;
import jsr292.weaver.opt.OptimizerVarAllocator;

class MHFolder extends MethodHandle {
    private final MethodHandle target;
    private final MethodHandle combiner;
    private final int combinerLength;
    
    public MHFolder(MethodHandle target, MethodHandle combiner, MethodType type) {
        super(type);
        this.target = target;
        this.combiner = combiner;
        this.combinerLength = combiner.type().parameterCount();
    }
    
    @Override
    public Object $invoke$() throws Throwable {
        return target.$invoke$(combiner.$invoke$());
    }
    
    @Override
    public Object $invoke$(Object o1) throws Throwable {
	Object combinerValue;
	switch(combinerLength) {
	case 0:
	    combinerValue = combiner.$invoke$();
	    break;
	case 1:
	    combinerValue = combiner.$invoke$(o1);
	    break;
	default:
	    throw super.assertion();
	}
        return target.$invoke$(combinerValue, o1);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2) throws Throwable {
	Object combinerValue;
	switch(combinerLength) {
	case 0:
	    combinerValue = combiner.$invoke$();
	    break;
	case 1:
	    combinerValue = combiner.$invoke$(o1);
	    break;
	case 2:
	    combinerValue = combiner.$invoke$(o1, o2);
	    break;
	default:
	    throw super.assertion();
	}
        return target.$invoke$(combinerValue, o1, o2);
    }
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3) throws Throwable {
	Object combinerValue;
	switch(combinerLength) {
	case 0:
	    combinerValue = combiner.$invoke$();
	    break;
	case 1:
	    combinerValue = combiner.$invoke$(o1);
	    break;
	case 2:
	    combinerValue = combiner.$invoke$(o1, o2);
	    break;
	case 3:
	    combinerValue = combiner.$invoke$(o1, o2, o3);
	    break;
	default:
	    throw super.assertion();
	}
        return target.$invoke$(combinerValue, o1, o2, o3);
    }
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4) throws Throwable {
	Object combinerValue;
	switch(combinerLength) {
	case 0:
	    combinerValue = combiner.$invoke$();
	    break;
	case 1:
	    combinerValue = combiner.$invoke$(o1);
	    break;
	case 2:
	    combinerValue = combiner.$invoke$(o1, o2);
	    break;
	case 3:
	    combinerValue = combiner.$invoke$(o1, o2, o3);
	    break;
	case 4:
	    combinerValue = combiner.$invoke$(o1, o2, o3, o4);
	    break;
	default:
	    throw super.assertion();
	}
        return target.$invoke$(combinerValue, o1, o2, o3, o4);
    }
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5) throws Throwable {
	Object combinerValue;
	switch(combinerLength) {
	case 0:
	    combinerValue = combiner.$invoke$();
	    break;
	case 1:
	    combinerValue = combiner.$invoke$(o1);
	    break;
	case 2:
	    combinerValue = combiner.$invoke$(o1, o2);
	    break;
	case 3:
	    combinerValue = combiner.$invoke$(o1, o2, o3);
	    break;
	case 4:
	    combinerValue = combiner.$invoke$(o1, o2, o3, o4);
	    break;
	case 5:
	    combinerValue = combiner.$invoke$(o1, o2, o3, o4, o5);
	    break;
	default:
	    throw super.assertion();
	}
        return target.$invoke$(combinerValue, o1, o2, o3, o4, o5);
    }
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6) throws Throwable {
	Object combinerValue;
	switch(combinerLength) {
	case 0:
	    combinerValue = combiner.$invoke$();
	    break;
	case 1:
	    combinerValue = combiner.$invoke$(o1);
	    break;
	case 2:
	    combinerValue = combiner.$invoke$(o1, o2);
	    break;
	case 3:
	    combinerValue = combiner.$invoke$(o1, o2, o3);
	    break;
	case 4:
	    combinerValue = combiner.$invoke$(o1, o2, o3, o4);
	    break;
	case 5:
	    combinerValue = combiner.$invoke$(o1, o2, o3, o4, o5);
	    break;
	case 6:
	    combinerValue = combiner.$invoke$(o1, o2, o3, o4, o5, o6);
	    break;
	default:
	    throw super.assertion();
	}
        return target.$invoke$(combinerValue, o1, o2, o3, o4, o5, o6);
    }
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7) throws Throwable {
	Object combinerValue;
	switch(combinerLength) {
	case 0:
	    combinerValue = combiner.$invoke$();
	    break;
	case 1:
	    combinerValue = combiner.$invoke$(o1);
	    break;
	case 2:
	    combinerValue = combiner.$invoke$(o1, o2);
	    break;
	case 3:
	    combinerValue = combiner.$invoke$(o1, o2, o3);
	    break;
	case 4:
	    combinerValue = combiner.$invoke$(o1, o2, o3, o4);
	    break;
	case 5:
	    combinerValue = combiner.$invoke$(o1, o2, o3, o4, o5);
	    break;
	case 6:
	    combinerValue = combiner.$invoke$(o1, o2, o3, o4, o5, o6);
	    break;
	case 7:
	    combinerValue = combiner.$invoke$(o1, o2, o3, o4, o5, o6, o7);
	    break;
	default:
	    throw super.assertion();
	}
        return target.$invoke$(combinerValue, o1, o2, o3, o4, o5, o6, o7);
    }
    
    @Override
    public Object $invoke$(Object... rest) throws Throwable {
        Object[] combinerArray;
        int length = rest.length;
	if (combinerLength == length)
            combinerArray = rest;
        else
            combinerArray = ArrayUtils.copyOf(rest, combinerLength);
        
        Object[] array = new Object[length + 1];
        System.arraycopy(rest, 0, array, 1, length);
	array[0] = combiner.$invoke$(combinerArray);
        return target.$invoke$(array);
    }
    
    @Override
    public boolean isWeavable(Class<?> classBeingRedefined) {
        return target.isWeavable(classBeingRedefined) && combiner.isWeavable(classBeingRedefined);
    }
    @Override
    public void weave(OptimizerContext context, OptimizerVarAllocator allocator) {
        if (Debug.DEBUG) {
            Debug.checkAllocator(allocator, this);
        }
        
        OptimizerSnippets.fold(context, allocator, target, combiner);
    }
}
